"""Runs all unittests for all modules at once."""

__author__ = "Gabriel Taubman (gtaubman@mac.com)"

import sys
import time
import unittest

# Modules to test:
modules = ["board", "tile_bag", "move", "constants", "word_list",
           "game", "game_runner", "test_players" ]



class AggregateTestRunner:
  """A test runner class that only prints aggregated summary information.
  
  Example output:

  Running 4 tests from tile_bag
  testCustomTileConfig (tile_bag.TileBagTest)                        PASS
  testDefaultConfiguration (tile_bag.TileBagTest)                    PASS
  testEmptyCustomTileConfig (tile_bag.TileBagTest)                   PASS
  testTwoBagsAreDifferent (tile_bag.TileBagTest)                     PASS

  Running 1 tests from board
  testLetterScores (board.BoardTest)                                 FAIL

  -----------------------------------------------------------------------

  Total time taken was 0.006s
  1 of 5 tests failed.
  """
  # How far to indent individual test names.
  indent = 2

  # After printing the test name, add this many extra spaces before showing the
  # PASS/FAIL.
  space_after_test = 10

  def __init__(self):
    self.suites = []
    self.failing_tests = []

  def AddSuites(self, suites, name): 
    # Suites can have suites inside of them.  Break them out.
    for suite in suites:
      # All of the tests in this suite are actual test cases, so grab the name
      # and module of one of them and use that as the name for the suite.
      case = suite._tests[0]
      self.suites.append((suite, "%s.%s" % (case.__class__.__module__,
                                            case.__class__.__name__)))

  def LongestTestName(self):
    """Returns the length of the name of the longest test."""
    longest = 0
    for suite, name in self.suites:
      for test in suite:
        if sys.version_info[1] < 5:
          # In versions of python less than 2.5 _testMethodName is called
          # _TestCase__testMethodName.
          longest = max(longest, len(test._TestCase__testMethodName))
        else:
          longest = max(longest, len(test._testMethodName))
    return longest

  def Run(self):
    """Runs all tests of all test suites added with AddSuite()."""
    combined_duration = 0
    succeeded = 0
    failed = 0

    # Prepare our printing strings.
    pass_str = "%sPASS%s" % (self.color["green"], self.color["default"])
    fail_str = "%sFAIL%s" % (self.color["red"], self.color["default"])

    # Grab the longest name, but don't let it get too long.
    longest_name = min(self.LongestTestName(), 70)
    printer_str = "%%-%ds %%%ds" % (longest_name,
                                    self.space_after_test + len(pass_str))

    for suite, name in self.suites:
      print "Running %d tests from %s" % (suite.countTestCases(), name)
      for test in suite:
        if sys.version_info[1] < 5:
          test_name = test._TestCase__testMethodName
        else:
          test_name = test._testMethodName
        result = unittest.TestResult()
        start_time = time.time()
        test.run(result)
        if result.wasSuccessful():
          outcome = pass_str
          succeeded += 1
        else:
          outcome = fail_str
          self.failing_tests.append("%s%s %s%s" % (self.color["red"],
                                                   name,
                                                   test_name,
                                                   self.color["default"]))
          failed += 1

        print " " * self.indent + printer_str % (test_name, outcome)
        stop_time = time.time()
        duration = stop_time - start_time
        combined_duration += duration
      print
    print "-" * (self.indent +
                 longest_name +
                 self.space_after_test + 1 +
                 len("PASS"))
    print
    print "Total time taken was %.3fs" % combined_duration
    if failed == 0:
      print "All %d tests succeeded." % succeeded
    else:
      print "%d of %d tests failed." % (failed, failed + succeeded)
      print
      print "The following tests failed:"
      for name in self.failing_tests:
        print " " * self.indent + name
    print

  color = { 'default'    :    "\033[0m",
            'bold'       :    "\033[1m",
            'underline'  :    "\033[4m",
            'blink'      :    "\033[5m",
            'reverse'    :    "\033[7m",
            'concealed'  :    "\033[8m",

            'black'      :    "\033[30m", 
            'red'        :    "\033[31m",
            'green'      :    "\033[32m",
            'yellow'     :    "\033[33m",
            'blue'       :    "\033[34m",
            'magenta'    :    "\033[35m",
            'cyan'       :    "\033[36m",
            'white'      :    "\033[37m",

            'on_black'   :    "\033[40m", 
            'on_red'     :    "\033[41m",
            'on_green'   :    "\033[42m",
            'on_yellow'  :    "\033[43m",
            'on_blue'    :    "\033[44m",
            'on_magenta' :    "\033[45m",
            'on_cyan'    :    "\033[46m",
            'on_white'   :    "\033[47m" }

# Run all tests.
if __name__ == "__main__":
  loader = unittest.TestLoader()
  a = AggregateTestRunner()
  for module_name in modules:
    to_run = __import__(module_name)
    a.AddSuites(loader.loadTestsFromModule(to_run), module_name)
  a.Run()
  sys.exit(len(a.failing_tests))

